from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLineEdit, QFileDialog, QMessageBox
import sqlite3
from PyQt6.QtCore import QStringListModel, qDebug
import os
import struct
from collections import defaultdict
import numpy as np
import random
from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QGroupBox, QWidget
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
import vtk
import 机械臂控制
import time
import sys
import ASCLL码STL转二进制脚本

point_list = [1]
point_normal_list = [1]
pos = [0, 0, 0]
fourpos = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
count_four = 0
point_index_map = dict()

def sgn(x):
    if x > 0:
        return 1
    elif x < 0:
        return -1
    else:
        return 0

def pass_point_list():
    return point_list.copy()

def pass_point_normal_list():
    return point_normal_list.copy()


def cpyint(byte_list):
    # 从列表中提取4个字节
    byte = byte_list[0: 4]
    del byte_list[:4]
    # 将字节转换为整数
    # 假设字节序为大端序，如果为小端序，需要设置byteorder='little'
    cpy = int.from_bytes(byte, byteorder='little')
    return cpy


def cpyfloat(byte_list):
    try:
        # 从列表中提取4个字节，并转换为 bytes
        byte = bytes(byte_list[0:4])  # 转换为 bytes 对象
        del byte_list[:4]
        # 将字节转换为浮点数，使用小端序
        cpy = struct.unpack('<f', byte)[0]
        return float(cpy)
    except Exception as e:
        print("Error:", e)


def openfile(FilePath):
    file_path, _ = QFileDialog.getOpenFileName(None, "选择文件", "", "所有文件 (*)")
    if file_path:
        filepath = file_path.split(".")
        if filepath[-1] == "stl" or filepath[-1] == "db":
            FilePath.setText(file_path)
        else:
            QMessageBox.warning(None, "警告", "请选择STL或DB文件！")


def vtk_make_db(vtk_file, angle):
    if vtk_file.text().split(".")[-1] != "stl":
        QMessageBox.warning(None, "警告", "请选择STL文件！")
    else:
        print("开始生成数据库")
        with open(vtk_file.text(), 'rb') as f1:
            header = f1.read(80)  # STL 文件的前 80 字节是头部
        if header.startswith(b'solid'):
            new_file = vtk_file.text().split(".")
            new_file[-2] = new_file[-2] + str("with_2_bit")
            new_file = ".".join(new_file)
            ASCLL码STL转二进制脚本.ascll_to_binary(vtk_file.text(), new_file)
            QMessageBox.warning(None, "提示", "选择的文件为ASCLL格式，已转换为二进制格式。")
            # QMessageBox是静态方法，不用限制对象，直接调用就行
        else:
            with open(vtk_file.text(), 'rb') as f:
                byte_data = f.read()  # 读取文件内容到一个字节串
                byte_list = list(byte_data)
                print(f"文件大小：{len(byte_list)}")
                del byte_list[:80]
            stlread = STLRead(vtk_file.text(), angle, byte_list)
            stlread.open_database()


class STLRead:
    def __init__(self, filename, angle, byte_list):
        self.filename = filename
        self.angle = angle
        self.byte_list = byte_list
        self.triangle_count = cpyint(self.byte_list)
        self.db = None
        self.cursor = None
        self.point_coords = {}  # 点ID到坐标的映射
        self.face_points = {}  # 面ID到点ID列表的映射

    def open_database(self):
        # 生成数据库文件名
        base_name = os.path.splitext(self.filename)[0]
        db_name = f"{base_name}在{self.angle}偏离角度下.db"

        # 确保数据库文件存在
        if os.path.exists(db_name):
            os.remove(db_name)
        open(db_name, 'w').close()

        # 连接数据库
        try:
            self.db = sqlite3.connect(db_name)
            self.db.execute("PRAGMA synchronous = OFF;")
            self.db.execute("PRAGMA journal_mode = MEMORY;")
            self.cursor = self.db.cursor()
            self.create_tables()
            self.insert_data_optimized()
        except sqlite3.Error as e:
            print(f"数据库错误: {e}")
        finally:
            if self.db:
                self.db.close()

    def create_tables(self):
        """一次性创建所有表"""
        tables = [
            """CREATE TABLE point (
                id INTEGER PRIMARY KEY,
                x REAL, y REAL, z REAL,
                UNIQUE(x, y, z)
            );""",
            """CREATE TABLE edge1 (
                id INTEGER PRIMARY KEY,
                pointid1 INTEGER, pointid2 INTEGER,
                UNIQUE(pointid1, pointid2)
            );""",
            """CREATE TABLE edge2 (
                id INTEGER,
                surface INTEGER
            );""",
            """CREATE TABLE edge3 (
                id INTEGER PRIMARY KEY,
                if_is_real_edge BOOLEAN DEFAULT FALSE,
                real_surface_no1 INTEGER,
                real_surface_no2 INTEGER
            );""",
            """CREATE TABLE surface1 (
                id INTEGER,
                edge INTEGER,
                neighbor_surface INTEGER,
                cos_normal REAL
            );""",
            """CREATE TABLE surface2 (
                id INTEGER,
                point INTEGER
            );""",
            """CREATE TABLE surface3 (
                id INTEGER PRIMARY KEY,
                normalx REAL, normaly REAL, normalz REAL,
                real_surface_no INTEGER,
                area REAL
            );""",
            """CREATE TABLE datadetail (
                id INTEGER,
                px REAL, py REAL, pz REAL,
                colorid INTEGER
            );"""
        ]

        for table_sql in tables:
            self.cursor.execute(table_sql)

        # 创建索引以加速查询
        self.cursor.execute("CREATE INDEX idx_point_coords ON point(x, y, z);")
        self.cursor.execute("CREATE INDEX idx_surface2_id ON surface2(id);")
        self.cursor.execute("CREATE INDEX idx_surface2_point ON surface2(point);")
        self.cursor.execute("CREATE INDEX idx_edge1_points ON edge1(pointid1, pointid2);")

    def insert_data_optimized(self):
        """优化后的数据插入方法"""
        # ===== 阶段1: 批量插入点和面数据 =====
        self.db.execute("BEGIN TRANSACTION")

        # 预编译SQL语句
        insert_point = "INSERT OR IGNORE INTO point (x, y, z) VALUES (?, ?, ?);"
        insert_surface3 = "INSERT INTO surface3 (id, normalx, normaly, normalz) VALUES (?, ?, ?, ?);"
        insert_surface2 = "INSERT INTO surface2 (id, point) VALUES (?, ?);"

        # 点坐标缓存 (x,y,z) -> id
        point_cache = {}

        # 边关系缓存 {(min_pid, max_pid): edge_id}
        edge_cache = {}

        # 面积计算缓存 {face_id: area}
        area_cache = {}

        # 批量插入点数据
        point_values = []
        surface3_values = []
        surface2_values = []
        edge_values = []

        start_time = time.time()

        for i in range(1, self.triangle_count + 1):
            if len(self.byte_list) < 50:
                break

            # 读取法向量
            normal = [cpyfloat(self.byte_list) for _ in range(3)]
            surface3_values.append((i, normal[0], normal[1], normal[2]))

            # 处理三个顶点
            face_points = []
            for _ in range(3):
                x, y, z = cpyfloat(self.byte_list), cpyfloat(self.byte_list), cpyfloat(self.byte_list)
                point_values.append((x, y, z))
                face_points.append((x, y, z))

            # 保存面点关系用于后续计算
            self.face_points[i] = face_points

            # 跳过属性字节
            del self.byte_list[:2]

        # 批量插入点
        self.cursor.executemany(insert_point, point_values)
        print(f"插入点数据完成，耗时: {time.time() - start_time:.2f}秒")

        # 获取点ID映射
        self.cursor.execute("SELECT id, x, y, z FROM point;")
        for row in self.cursor.fetchall():
            self.point_coords[(row[1], row[2], row[3])] = row[0]

        # 批量插入面数据
        self.cursor.executemany(insert_surface3, surface3_values)
        print(f"插入面数据完成，耗时: {time.time() - start_time:.2f}秒")

        # 准备surface2数据
        for i, points in self.face_points.items():
            for point in points:
                pid = self.point_coords[point]
                surface2_values.append((i, pid))

        # 批量插入surface2
        self.cursor.executemany(insert_surface2, surface2_values)
        print(f"插入面点关系完成，耗时: {time.time() - start_time:.2f}秒")

        # 准备边数据
        edge_inserts = []
        edge2_inserts = []

        for i, points in self.face_points.items():
            # 获取点ID
            pids = [self.point_coords[p] for p in points]
            sorted_pids = sorted(pids)

            # 创建三条边
            edges = [
                (min(sorted_pids[0], sorted_pids[1]), max(sorted_pids[0], sorted_pids[1])),
                (min(sorted_pids[0], sorted_pids[2]), max(sorted_pids[0], sorted_pids[2])),
                (min(sorted_pids[1], sorted_pids[2]), max(sorted_pids[1], sorted_pids[2]))
            ]

            for p1, p2 in edges:
                edge_key = (p1, p2)
                if edge_key not in edge_cache:
                    edge_inserts.append((p1, p2))
                    edge_cache[edge_key] = len(edge_inserts)  # 临时ID
                edge2_inserts.append((edge_cache[edge_key], i))

        # 批量插入边
        if edge_inserts:
            self.cursor.executemany(
                "INSERT OR IGNORE INTO edge1 (pointid1, pointid2) VALUES (?, ?);",
                edge_inserts
            )
            print(f"插入边数据完成，耗时: {time.time() - start_time:.2f}秒")

            # 获取实际边ID
            self.cursor.execute("SELECT id, pointid1, pointid2 FROM edge1;")
            for row in self.cursor.fetchall():
                edge_cache[(min(row[1], row[2]), max(row[1], row[2]))] = row[0]

            # 更新edge2中的实际边ID
            edge2_values = []
            for temp_id, face_id in edge2_inserts:
                # 找到对应的边键
                for key, id_val in edge_cache.items():
                    if id_val == temp_id:
                        edge2_values.append((id_val, face_id))
                        break

            # 批量插入edge2
            self.cursor.executemany(
                "INSERT INTO edge2 (id, surface) VALUES (?, ?);",
                edge2_values
            )

        self.db.commit()
        print(f"阶段1总耗时: {time.time() - start_time:.2f}秒")

        # ===== 阶段2: 构建邻接关系和真实边 =====
        start_time = time.time()
        self.db.execute("BEGIN TRANSACTION")

        # 构建邻接图
        self.cursor.execute("""
            INSERT INTO surface1 (id, edge, neighbor_surface)
            SELECT e1.surface, e1.id, e2.surface
            FROM edge2 e1
            JOIN edge2 e2 ON e1.id = e2.id AND e1.surface < e2.surface
        """)

        # 计算法向量夹角余弦
        self.cursor.execute("""
            UPDATE surface1
            SET cos_normal = (
                SELECT (a.normalx * b.normalx + a.normaly * b.normaly + a.normalz * b.normalz)
                FROM surface3 a, surface3 b
                WHERE a.id = surface1.id AND b.id = surface1.neighbor_surface
            )
        """)

        # 标记真实边 (夹角大于阈值的边)
        self.cursor.execute("""
            INSERT INTO edge3 (id, if_is_real_edge, real_surface_no1, real_surface_no2)
            SELECT 
                edge, 
                TRUE, 
                MIN(id, neighbor_surface), 
                MAX(id, neighbor_surface)
            FROM surface1
            WHERE cos_normal <= ?
        """, (np.cos(self.angle)*np.pi/180,))

        self.db.commit()
        print(f"阶段2耗时: {time.time() - start_time:.2f}秒")

        # ===== 阶段3: 曲面连通分量分析 =====
        start_time = time.time()
        self.db.execute("BEGIN TRANSACTION")

        # 构建邻接表 (只考虑小于阈值的邻接面)
        self.cursor.execute("""
            SELECT id, neighbor_surface 
            FROM surface1 
            WHERE cos_normal > ?
        """, (np.cos(self.angle*np.pi/180),))

        graph = defaultdict(list)
        for row in self.cursor.fetchall():
            face1, face2 = row
            graph[face1].append(face2)
            graph[face2].append(face1)

        # BFS标记连通分量
        visited = set()
        surface_id = 0
        surface_mapping = {}

        for face in range(1, self.triangle_count + 1):
            if face not in visited:
                surface_id += 1
                queue = [face]
                visited.add(face)

                while queue:
                    current = queue.pop(0)
                    surface_mapping[current] = surface_id
                    for neighbor in graph.get(current, []):
                        if neighbor not in visited:
                            visited.add(neighbor)
                            queue.append(neighbor)

        # 更新曲面ID
        update_data = [(surface_mapping.get(i, 0), i) for i in range(1, self.triangle_count + 1)]
        self.cursor.executemany(
            "UPDATE surface3 SET real_surface_no = ? WHERE id = ?",
            update_data
        )

        self.db.commit()
        print(f"阶段3耗时: {time.time() - start_time:.2f}秒")

        # ===== 阶段4: 计算面积和填充详情 =====
        start_time = time.time()
        self.db.execute("BEGIN TRANSACTION")

        # 高效计算面积
        area_values = []
        for face_id, points in self.face_points.items():
            if len(points) != 3:
                continue

            # 获取三个点的坐标
            p1, p2, p3 = [np.array(p) for p in points]

            # 计算向量
            v1 = p2 - p1
            v2 = p3 - p1

            # 计算叉积的模
            cross = np.cross(v1, v2)
            area = 0.5 * np.linalg.norm(cross)

            area_values.append((area, face_id))

        # 批量更新面积
        self.cursor.executemany(
            "UPDATE surface3 SET area = ? WHERE id = ?",
            area_values
        )

        # 填充详情表
        self.cursor.execute("""
            INSERT INTO datadetail (id, px, py, pz, colorid)
            SELECT 
                s2.id, 
                p.x, p.y, p.z, 
                s3.real_surface_no
            FROM surface2 s2
            JOIN point p ON s2.point = p.id
            JOIN surface3 s3 ON s2.id = s3.id
        """)

        # 更新edge3数据
        self.cursor.execute("""
            UPDATE edge3
            SET real_surface_no1 = (
                SELECT s3.real_surface_no
                FROM surface3 s3
                WHERE s3.id = edge3.real_surface_no1
            )
        """)

        self.cursor.execute("""
            UPDATE edge3
            SET real_surface_no2 = (
                SELECT s3.real_surface_no
                FROM surface3 s3
                WHERE s3.id = edge3.real_surface_no2
            )
        """)

        self.db.commit()
        print(f"阶段4耗时: {time.time() - start_time:.2f}秒")


def vtk_make_db_ui(vtk_file, VTKWidget, MainWindow, QPushButton, scan_gap):
    if vtk_file.split(".")[-1] != "db":
        print("请选择DB文件！")
    else:
        # 创建或更新 VTK 渲染窗口
        if not hasattr(MainWindow, "vtk_viewer"):  # 这个函数是python检查是对象中否有某个属性，如果没有则返回false，如果有则返回true
            # 第一次创建时初始化布局
            layout = QVBoxLayout(VTKWidget)
            group_box = QGroupBox("被测物体三维模型", MainWindow)
            layout.addWidget(group_box)
            group_layout = QVBoxLayout()
            group_box.setLayout(group_layout)
            # 创建 VTK Widget
            vtk_widget = QVTKRenderWindowInteractor(MainWindow)
            group_layout.addWidget(vtk_widget)
            MainWindow.vtk_widget = vtk_widget
        else:
            # 如果已经创建过，直接获取 vtk_widget
            vtk_widget = MainWindow.vtk_widget
            # 清空渲染器，避免残留
            MainWindow.vtk_viewer.renderer.RemoveAllViewProps()
            MainWindow.vtk_viewer.style = None
            # 把三点定位和旋转矩阵也重置一下
            global pos, fourpos, count_four
            机械臂控制.RotaionMatrix = np.full((3, 3), None)
            机械臂控制.ZeroPoint = np.array([])
            pos = [0, 0, 0]
            fourpos = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
            count_four = 0
            MainWindow.P1X.setText(str(fourpos[0][0]))
            MainWindow.P1Y.setText(str(fourpos[0][1]))
            MainWindow.P1Z.setText(str(fourpos[0][2]))
            MainWindow.P2X.setText(str(fourpos[1][0]))
            MainWindow.P2Y.setText(str(fourpos[1][1]))
            MainWindow.P2Z.setText(str(fourpos[1][2]))
            MainWindow.P3X.setText(str(fourpos[2][0]))
            MainWindow.P3Y.setText(str(fourpos[2][1]))
            MainWindow.P3Z.setText(str(fourpos[2][2]))
            MainWindow.P4X.setText(str(fourpos[3][0]))
            MainWindow.P4Y.setText(str(fourpos[3][1]))
            MainWindow.P4Z.setText(str(fourpos[3][2]))
        # 连接数据库
        conn = sqlite3.connect(vtk_file)
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM datadetail')
        rows = cursor.fetchall()
        # 数据库中所有数据
        data_list = [list(row) for row in rows]
        cursor.execute('SELECT * FROM surface3')
        rows2 = cursor.fetchall()
        # 数据库中所有数据
        normal_list = [list(row2) for row2 in rows2]
        cursor.execute('SELECT id,real_surface_no1, real_surface_no2 FROM edge3')
        rows3 = cursor.fetchall()
        # 数据库中所有数据
        edge_list = [list(row3) for row3 in rows3]
        cursor.close()
        conn.close()
        # 生成随机颜色,要求RGB的值在0.1到0.9之间，这样以后得点可以用黑色和白色而不冲突
        color = [np.random.uniform(0.1, 0.7, 3).tolist() for _ in range(len(set([row[4] for row in data_list])))]
        vtknew = vtkviewer(vtk_file, vtk_widget, color, data_list, normal_list, edge_list,angle=None, scangap=None,
                           mainwindow=MainWindow)
        vtknew.create_model_viewer()
        # 绑定到 MainWindow，便于下次清理
        MainWindow.vtk_viewer = vtknew
        try:
            QPushButton.clicked.disconnect()
        except TypeError:
            print("No button to disconnect")
            pass
        QPushButton.clicked.connect(lambda: vtknew.style.MakePoint(scan_gap.value()))


class vtkviewer():
    def __init__(self, vtk_file, vtkWidget, color, data_list, normal_list, edge_list, angle=None, scangap=None, mainwindow=None):
        self.vtk_file = vtk_file
        self.vtkWidget = vtkWidget
        self.color = color
        self.data_list = data_list
        self.normal_list = normal_list
        self.edge_list = edge_list
        self.angle = angle
        self.scangap = scangap
        self.renderer = vtk.vtkRenderer()
        self.vtkWidget.GetRenderWindow().AddRenderer(self.renderer)
        self.interactor = self.vtkWidget.GetRenderWindow().GetInteractor()
        self.actors = []  # 用来存储所有actor的列表
        self.style = None
        self.mainwindow = mainwindow

    def create_model_viewer(self):
        # ===== 1. 合并所有三角形数据 =====
        points = vtk.vtkPoints()
        triangles = vtk.vtkCellArray()
        colors = vtk.vtkUnsignedCharArray()
        colors.SetNumberOfComponents(3)  # RGB颜色

        for i in range(len(self.data_list) // 3):
            # 添加三个顶点
            pid0 = points.InsertNextPoint(*map(float, self.data_list[3 * i][1:4]))
            pid1 = points.InsertNextPoint(*map(float, self.data_list[3 * i + 1][1:4]))
            pid2 = points.InsertNextPoint(*map(float, self.data_list[3 * i + 2][1:4]))

            # 创建三角形单元
            triangle = vtk.vtkTriangle()
            triangle.GetPointIds().SetId(0, pid0)
            triangle.GetPointIds().SetId(1, pid1)
            triangle.GetPointIds().SetId(2, pid2)
            triangles.InsertNextCell(triangle)

            # 添加颜色数据
            color_idx = int(self.data_list[3 * i][4]) - 1
            r, g, b = [int(c * 255) for c in self.color[color_idx]]
            colors.InsertNextTuple3(r, g, b)

        # ===== 2. 创建单一PolyData =====
        polyData = vtk.vtkPolyData()
        polyData.SetPoints(points)
        polyData.SetPolys(triangles)
        polyData.GetCellData().SetScalars(colors)  # 关联颜色数据

        # ===== 3. 创建Mapper和Actor =====
        mapper = vtk.vtkPolyDataMapper()
        mapper.SetInputData(polyData)

        actor = vtk.vtkActor()
        actor.SetMapper(mapper)
        actor.GetProperty().lighting = True  # 启用光照

        # 设置边缘可见性和颜色
        actor.GetProperty().SetEdgeColor(0, 0, 0)  # 黑色边缘
        actor.GetProperty().SetLineWidth(2)  # 边缘线宽
        actor.GetProperty().EdgeVisibilityOn()

        # ===== 4. 添加到渲染器 =====
        self.renderer.AddActor(actor)
        self.actors = [actor]  # 更新actors列表

        # ===== 5. 统一设置交互器 (移到循环外) =====
        self.style = CustomInteractorStyle(
            self.data_list,
            self.normal_list,
            actors=self.actors,
            mainwindow=self.mainwindow,
            color_table=self.color
        )
        self.style.SetDefaultRenderer(self.renderer)
        self.interactor.SetInteractorStyle(self.style)

        # ===== 6. 统一初始化设置 =====
        self.renderer.SetBackground(0.7, 0.7, 0.7)  # 单一背景色设置
        self.renderer.ResetCamera()
        self.interactor.Initialize()  # 只需初始化一次


class CustomInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):
    def __init__(self, data_list, normal_list, parent=None, actors=None, mainwindow=None, color_table=None):
        super(CustomInteractorStyle, self).__init__()
        self.observer_id = self.AddObserver("LeftButtonPressEvent", self.on_left_button_down1)
        self.picker = vtk.vtkCellPicker()
        self.picker.SetTolerance(0.0005)
        self.actors = actors  # 现在只有一个actor
        self.selected_color_ids = set()
        self.data_list = data_list
        self.normal_list = normal_list
        self.mainwindow = mainwindow
        self.pointPolyData = vtk.vtkPolyData()
        self.color_table = color_table  # 颜色查找表
        self.original_colors = None  # 存储原始颜色数据

    def store_original_colors(self):
        """存储所有三角形的原始颜色"""
        if self.original_colors is None and self.actors:
            actor = self.actors[0]
            mapper = actor.GetMapper()
            if mapper:
                polyData = mapper.GetInput()
                if polyData:
                    color_array = polyData.GetCellData().GetScalars()
                    self.original_colors = vtk.vtkUnsignedCharArray()
                    self.original_colors.DeepCopy(color_array)

    def on_left_button_down1(self, obj, event):
        click_pos = self.GetInteractor().GetEventPosition()
        self.picker.Pick(click_pos[0], click_pos[1], 0, self.GetDefaultRenderer())
        actor = self.picker.GetActor()

        if not actor or not self.actors or actor != self.actors[0]:
            self.OnLeftButtonDown()
            return

        triangle_index = self.picker.GetCellId()
        if triangle_index == -1:
            self.OnLeftButtonDown()
            return

        # 确保已存储原始颜色
        self.store_original_colors()
        if not self.original_colors:
            self.OnLeftButtonDown()
            return

        # 获取被点击三角形的颜色ID
        clicked_color_id = int(self.data_list[3 * triangle_index][4])
        print(f"Clicked Triangle Color ID: {clicked_color_id}")

        # 获取当前颜色数组
        mapper = actor.GetMapper()
        polyData = mapper.GetInput()
        color_array = polyData.GetCellData().GetScalars()

        # 检查该颜色ID是否已被选中
        is_currently_selected = clicked_color_id in self.selected_color_ids

        # 更新所有相同颜色ID的三角形
        for i in range(len(self.data_list) // 3):
            color_id = int(self.data_list[3 * i][4])
            if color_id == clicked_color_id:
                if is_currently_selected:
                    # 恢复原始颜色
                    r, g, b = self.original_colors.GetTuple3(i)
                    color_array.SetTuple3(i, r, g, b)
                else:
                    # 设置为白色
                    color_array.SetTuple3(i, 255, 255, 255)

        # 更新选择状态
        if is_currently_selected:
            self.selected_color_ids.remove(clicked_color_id)
        else:
            self.selected_color_ids.add(clicked_color_id)

        # 标记数据已修改并更新渲染
        polyData.Modified()
        mapper.Update()
        self.GetInteractor().GetRenderWindow().Render()
        self.OnLeftButtonDown()

    def on_left_button_down2(self, obj, event):
        # 创建一个vtkCellPicker对象
        picker = vtk.vtkCellPicker()
        picker.SetTolerance(0.01)  # 设置选择器的容差，取决于点的密度和点的大小

        # 获取点击位置在屏幕上的坐标
        click_pos = self.GetInteractor().GetEventPosition()

        # 执行选择操作
        picker.Pick(click_pos[0], click_pos[1], 0, self.GetDefaultRenderer())

        # 检查是否拾取到某个点
        if picker.GetCellId() != -1:
            # 获取拾取到的点ID
            point_id = picker.GetPointId()
            if point_id != -1:
                # 获取点的坐标

                point_coords = self.pointPolyData.GetPoint(point_id)
                # print("Clicked point coordinates:", point_coords)
                # 获取颜色数组
                colors = self.pointPolyData.GetPointData().GetScalars()
                if colors:
                    # 获取当前点的颜色
                    current_color = colors.GetTuple3(point_id)
                    if current_color == (0, 0, 0):  # 当前颜色为黑色
                        # 修改点击点的颜色为绿色
                        colors.SetTuple3(point_id, 255, 180, 180)
                        self.mainwindow.SetPosition.setEnabled(True)
                        for i in range(3):
                            pos[i] = point_coords[i]
                        print(pos)
                        index = find_point_index(pos)
                        if index != None:
                            print(point_normal_list[index])
                    else:  # 当前颜色为粉色
                        # 修改点击点的颜色为黑色
                        colors.SetTuple3(point_id, 0, 0, 0)
                        self.mainwindow.SetPosition.setEnabled(False)
                        for i in range(3):
                            pos[i] = 0
                    self.pointPolyData.Modified()  # 标记数据已更新
        # 调用父类方法以确保正常处理其他鼠标事件
        self.OnLeftButtonDown()
        self.GetInteractor().GetRenderWindow().Render()

    def MakePoint(self, scan_gap):
        if not self.pointPolyData or not self.GetDefaultRenderer():
            print("Error: Point cloud or renderer is not initialized.")
            return
        print(self.selected_color_ids)
        global count_four
        count_four = 0

        # 采样参数
        step_x = scan_gap  # x方向的步长
        step_y = scan_gap  # y方向的步长

        # 存储采样点的列表
        sampled_points = []
        sampled_points_normal = []
        # 逐点采样
        if len(self.selected_color_ids) ==0:
            QMessageBox.warning(None, "提示", "未选中曲面！")
            return

        sampled_points = []
        sampled_points_normal = []

        for color_id in self.selected_color_ids:
            # 从数据中筛选出特定颜色ID的三角形
            triangles = [data for data in self.data_list if data[4] == color_id]  # 颜色ID在第五列
            # 遍历每个三角形
            for i in range(0, len(triangles), 3):
                # 三角形的三个顶点
                p1 = np.array(triangles[i][1:4])  # 顶点1的坐标 (px, py, pz)
                p2 = np.array(triangles[i + 1][1:4])  # 顶点2的坐标
                p3 = np.array(triangles[i + 2][1:4])  # 顶点3的坐标
                # 计算三角形的平面方程
                v1 = p2 - p1
                v2 = p3 - p1
                normal = np.cross(v1, v2)  # 计算法向量
                A, B, C = normal  # 法向量的分量
                D = -np.dot(normal, p1)  # D的计算

                # 平面三角形的三个顶点
                _p1 = np.array([p1[0], p1[1], 0])
                _p2 = np.array([p2[0], p2[1], 0])
                _p3 = np.array([p3[0], p3[1], 0])
                # 计算平面三角形的平面方程
                _v1 = _p2 - _p1
                _v2 = _p3 - _p1
                _normal = np.cross(_v1, _v2)  # 计算法向量

                # 设定采样范围，计算 x_min, x_max, y_min, y_max
                x_min = get_max_multiple(min(p1[0], p2[0], p3[0]), step_x)
                x_max = get_min_multiple(max(p1[0], p2[0], p3[0]), step_x)
                y_min = get_max_multiple(min(p1[1], p2[1], p3[1]), step_y)
                y_max = get_min_multiple(max(p1[1], p2[1], p3[1]), step_y)
                # 在x和y的范围内进行采样
                x = x_min
                num = 0
                while x <= x_max:
                    y = y_min
                    while y <= y_max:
                        # 判断点(x, y)是否在三角形内
                        # 使用重心坐标法
                        b1 = np.cross(_p2 - _p1, np.array([x, y, 0]) - _p1)
                        b2 = np.cross(_p3 - _p2, np.array([x, y, 0]) - _p2)
                        b3 = np.cross(_p1 - _p3, np.array([x, y, 0]) - _p3)

                        # 检查重心坐标是否全为正或全为负
                        if (np.dot(b1, _normal) >= 0 and
                                np.dot(b2, _normal) >= 0 and
                                np.dot(b3, _normal) >= 0):
                            # 计算z值
                            z = (-D - A * x - B * y) / C  # 计算z的值
                            sampled_points.append((x, y, z))  # 记录采样点
                            num = num + 1
                        y += step_y
                    x += step_x
                trinormal = self.normal_list[triangles[i][0] - 1][1:4]
                for _ in range(num):
                    sampled_points_normal.append(trinormal)

        global point_list, point_normal_list
        point_list = []
        point_normal_list = []
        point_list = sampled_points
        point_normal_list = sampled_points_normal
        # 将点和法向量打包在一起
        combined = list(zip(point_list, point_normal_list))

        # 按 x 从小到大排序，如果 x 相同，则根据 x/step_x 的奇偶性决定 y 的排序方式,这样就能做到蛇形扫描了
        combined.sort(key=lambda pair: (pair[0][0], sgn(pair[0][0]/step_x%2-0.5)*pair[0][1]))

        # 为点云去重
        seen = set()
        unique_combined = []
        for point, normal in combined:
            if tuple(point) not in seen:
                seen.add(tuple(point))  # 添加到 seen 集合
                unique_combined.append((point, normal))  # 添加到结果列表

        # 解包为排序后的列表
        point_list, point_normal_list = zip(*unique_combined)
        point_list = list(point_list)
        point_normal_list = list(point_normal_list)

        # 创建全局点索引映射
        global point_index_map
        point_index_map = {tuple(point): i for i, point in enumerate(point_list)}

        # 创建点数据结构
        points = vtk.vtkPoints()
        colors = vtk.vtkUnsignedCharArray()
        colors.SetNumberOfComponents(3)  # RGB 颜色
        colors.SetName("PointColors")

        # 添加点和初始颜色（黑色）
        for point in point_list:
            points.InsertNextPoint(point)
            colors.InsertNextTuple3(0, 0, 0)  # 初始黑色

        # 创建顶点单元
        vertices = vtk.vtkCellArray()
        for i in range(len(point_list)):
            vertices.InsertNextCell(1)
            vertices.InsertCellPoint(i)

        # 创建点PolyData
        self.pointPolyData = vtk.vtkPolyData()
        self.pointPolyData.SetPoints(points)
        self.pointPolyData.SetVerts(vertices)
        self.pointPolyData.GetPointData().SetScalars(colors)

        # 创建点数据的mapper和actor
        pointMapper = vtk.vtkPolyDataMapper()
        pointMapper.SetInputData(self.pointPolyData)

        pointActor = vtk.vtkActor()
        pointActor.SetMapper(pointMapper)
        pointActor.GetProperty().SetPointSize(5)  # 设置点的大小

        self.GetDefaultRenderer().AddActor(pointActor)
        self.GetInteractor().GetRenderWindow().Render()
        self.RemoveObserver(self.observer_id)
        self.AddObserver("LeftButtonPressEvent", self.on_left_button_down2)



def set_position(button, mainwindow):
    global pos, count_four, fourpos
    # 不足四个点则不允许计算旋转矩阵
    if count_four < 3:
        mainwindow.CalculateMatrix.setEnabled(False)
    else:
        mainwindow.CalculateMatrix.setEnabled(True)
    # 依此输入四个点的坐标
    if count_four == 4:
        reply = QMessageBox.question(None, "确认", "已经有四个点了，是否替代最初的点？",
                                     QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
                                     # 使用 StandardButton 枚举
                                     QMessageBox.StandardButton.No  # 默认按钮
                                     )
        if reply == QMessageBox.StandardButton.Yes:
            fourpos[0] = fourpos[1]
            fourpos[1] = fourpos[2]
            fourpos[2] = fourpos[3]
            fourpos[3] = pos.copy()
    else:
        fourpos[count_four] = pos.copy()
        count_four = count_four + 1
    button.setEnabled(False)
    mainwindow.P1X.setText(str(fourpos[0][0]))
    mainwindow.P1Y.setText(str(fourpos[0][1]))
    mainwindow.P1Z.setText(str(fourpos[0][2]))
    mainwindow.P2X.setText(str(fourpos[1][0]))
    mainwindow.P2Y.setText(str(fourpos[1][1]))
    mainwindow.P2Z.setText(str(fourpos[1][2]))
    mainwindow.P3X.setText(str(fourpos[2][0]))
    mainwindow.P3Y.setText(str(fourpos[2][1]))
    mainwindow.P3Z.setText(str(fourpos[2][2]))
    mainwindow.P4X.setText(str(fourpos[3][0]))
    mainwindow.P4Y.setText(str(fourpos[3][1]))
    mainwindow.P4Z.setText(str(fourpos[3][2]))


# 函数：获取不大于目标值的最大步长整数倍
def get_max_multiple(value, step):
    return value - (value % step)


# 函数：获取不小于目标值的最小步长整数倍
def get_min_multiple(value, step):
    return value + (step - (value % step)) if value % step != 0 else value


# 寻找点的索引
def find_point_index(target_point, tolerance=1e-6):
    for i, point in enumerate(point_list):
        if np.allclose(point, target_point, atol=tolerance):
            return i
    return None


# 辅助函数
def calculate_3d_area(p1, p2, p3):
    """计算三维空间三角形面积"""
    v1 = np.array(p2) - np.array(p1)
    v2 = np.array(p3) - np.array(p1)
    cross = np.cross(v1, v2)
    return np.linalg.norm(cross) / 2.0